"""
Configuration Parser for Extension Descriptors

Parses YAML/JSON configuration files that define extension behavior,
build steps, validation rules, and metadata.
"""

import json
import yaml
from pathlib import Path
from typing import Dict, List, Any, Optional, Union
from dataclasses import dataclass
import logging

logger = logging.getLogger(__name__)


@dataclass
class BuildStep:
    """Represents a single build step in the configuration."""
    name: str
    command: str
    working_dir: Optional[str] = None
    environment: Optional[Dict[str, str]] = None
    conditions: Optional[Dict[str, Any]] = None
    timeout: Optional[int] = None
    fail_fast: bool = True


@dataclass
class TestStep:
    """Represents a single test step in the configuration."""
    name: str
    command: str
    working_dir: Optional[str] = None
    environment: Optional[Dict[str, str]] = None
    expected_files: Optional[List[str]] = None
    timeout: Optional[int] = None


@dataclass
class ValidationRule:
    """Represents a validation rule in the configuration."""
    name: str
    type: str  # 'file_exists', 'command_success', 'regex_match', etc.
    target: str
    parameters: Optional[Dict[str, Any]] = None


@dataclass
class ExtensionDescriptor:
    """Complete extension descriptor parsed from configuration."""
    name: str
    version: str
    description: str
    supported_frameworks: List[str]
    dependencies: List[str] = None
    
    # Build configuration
    build_steps: List[BuildStep] = None
    build_artifacts: List[str] = None
    build_environment: Dict[str, str] = None
    
    # Test configuration  
    test_steps: List[TestStep] = None
    test_artifacts: List[str] = None
    
    # Validation configuration
    validation_rules: List[ValidationRule] = None
    
    # Detection patterns
    config_patterns: List[str] = None
    source_patterns: List[str] = None
    
    # Schema for user configurations
    config_schema: Dict[str, Any] = None
    
    # CLI commands provided by extension
    commands: Dict[str, Any] = None
    
    def __post_init__(self):
        """Initialize default values for optional fields."""
        if self.dependencies is None:
            self.dependencies = []
        if self.build_steps is None:
            self.build_steps = []
        if self.build_artifacts is None:
            self.build_artifacts = []
        if self.build_environment is None:
            self.build_environment = {}
        if self.test_steps is None:
            self.test_steps = []
        if self.test_artifacts is None:
            self.test_artifacts = []
        if self.validation_rules is None:
            self.validation_rules = []
        if self.config_patterns is None:
            self.config_patterns = []
        if self.source_patterns is None:
            self.source_patterns = []
        if self.config_schema is None:
            self.config_schema = {}
        if self.commands is None:
            self.commands = {}


class ExtensionConfigParser:
    """Parser for extension configuration files."""
    
    SUPPORTED_FORMATS = ['.yaml', '.yml', '.json']
    
    def __init__(self):
        """Initialize the parser."""
        self._schema_validators = {
            'file_exists': self._validate_file_exists_rule,
            'command_success': self._validate_command_success_rule,
            'regex_match': self._validate_regex_match_rule,
            'environment_var': self._validate_env_var_rule,
            'directory_not_empty': self._validate_directory_not_empty_rule,
            'executable_exists': self._validate_executable_exists_rule,
            'library_exists': self._validate_library_exists_rule,
        }
    
    def can_parse(self, config_path: Union[str, Path]) -> bool:
        """
        Check if the parser can handle the given configuration file.
        
        Args:
            config_path: Path to configuration file
            
        Returns:
            True if parser can handle this file format
        """
        path = Path(config_path)
        return path.suffix.lower() in self.SUPPORTED_FORMATS
    
    def parse(self, config_path: Union[str, Path]) -> ExtensionDescriptor:
        """
        Parse an extension configuration file.
        
        Args:
            config_path: Path to configuration file
            
        Returns:
            ExtensionDescriptor instance
            
        Raises:
            FileNotFoundError: If configuration file doesn't exist
            ValueError: If configuration is invalid
            yaml.YAMLError: If YAML parsing fails
            json.JSONDecodeError: If JSON parsing fails
        """
        path = Path(config_path)
        
        if not path.exists():
            raise FileNotFoundError(f"Extension configuration not found: {path}")
        
        if not self.can_parse(path):
            raise ValueError(f"Unsupported configuration format: {path.suffix}")
        
        # Load configuration data
        try:
            with open(path, 'r', encoding='utf-8') as f:
                if path.suffix.lower() == '.json':
                    config_data = json.load(f)
                else:  # YAML
                    config_data = yaml.safe_load(f)
        except Exception as e:
            raise ValueError(f"Failed to parse configuration file {path}: {e}")
        
        # Validate and parse configuration
        try:
            return self._parse_descriptor(config_data, path)
        except Exception as e:
            raise ValueError(f"Invalid extension configuration in {path}: {e}")
    
    def validate_descriptor(self, descriptor: ExtensionDescriptor) -> List[str]:
        """
        Validate an extension descriptor.
        
        Args:
            descriptor: ExtensionDescriptor to validate
            
        Returns:
            List of validation error messages (empty if valid)
        """
        errors = []
        
        # Validate required fields
        if not descriptor.name:
            errors.append("Extension name is required")
        
        if not descriptor.version:
            errors.append("Extension version is required")
        
        if not descriptor.description:
            errors.append("Extension description is required")
        
        if not descriptor.supported_frameworks:
            errors.append("At least one supported framework is required")
        
        # Validate build steps
        for i, step in enumerate(descriptor.build_steps):
            if not step.name:
                errors.append(f"Build step {i}: name is required")
            if not step.command:
                errors.append(f"Build step {i}: command is required")
        
        # Validate test steps
        for i, step in enumerate(descriptor.test_steps):
            if not step.name:
                errors.append(f"Test step {i}: name is required")
            if not step.command:
                errors.append(f"Test step {i}: command is required")
        
        # Validate validation rules
        for i, rule in enumerate(descriptor.validation_rules):
            if not rule.name:
                errors.append(f"Validation rule {i}: name is required")
            if not rule.type:
                errors.append(f"Validation rule {i}: type is required")
            if rule.type not in self._schema_validators:
                errors.append(f"Validation rule {i}: unsupported type '{rule.type}'")
            if not rule.target:
                errors.append(f"Validation rule {i}: target is required")
        
        return errors
    
    def _parse_descriptor(self, config_data: Dict[str, Any], config_path: Path) -> ExtensionDescriptor:
        """Parse configuration data into ExtensionDescriptor."""
        
        # Extract basic metadata
        name = config_data.get('name')
        version = config_data.get('version')
        description = config_data.get('description')
        supported_frameworks = config_data.get('supported_frameworks', [])
        dependencies = config_data.get('dependencies', [])
        
        if not name:
            raise ValueError("Extension name is required")
        if not version:
            raise ValueError("Extension version is required")
        if not description:
            raise ValueError("Extension description is required")
        
        # Parse build configuration
        build_config = config_data.get('build', {})
        build_steps = self._parse_build_steps(build_config.get('steps', []))
        build_artifacts = build_config.get('artifacts', [])
        build_environment = build_config.get('environment', {})
        
        # Parse test configuration
        test_config = config_data.get('test', {})
        test_steps = self._parse_test_steps(test_config.get('steps', []))
        test_artifacts = test_config.get('artifacts', [])
        
        # Parse validation configuration
        validation_config = config_data.get('validation', {})
        validation_rules = self._parse_validation_rules(validation_config.get('rules', []))
        
        # Parse detection patterns
        detection = config_data.get('detection', {})
        config_patterns = detection.get('config_patterns', [])
        source_patterns = detection.get('source_patterns', [])
        
        # Parse schema and commands
        config_schema = config_data.get('config_schema', {})
        commands = config_data.get('commands', {})
        
        return ExtensionDescriptor(
            name=name,
            version=version,
            description=description,
            supported_frameworks=supported_frameworks,
            dependencies=dependencies,
            build_steps=build_steps,
            build_artifacts=build_artifacts,
            build_environment=build_environment,
            test_steps=test_steps,
            test_artifacts=test_artifacts,
            validation_rules=validation_rules,
            config_patterns=config_patterns,
            source_patterns=source_patterns,
            config_schema=config_schema,
            commands=commands
        )
    
    def _parse_build_steps(self, steps_data: List[Dict[str, Any]]) -> List[BuildStep]:
        """Parse build steps from configuration data."""
        steps = []
        for step_data in steps_data:
            steps.append(BuildStep(
                name=step_data.get('name'),
                command=step_data.get('command'),
                working_dir=step_data.get('working_dir'),
                environment=step_data.get('environment'),
                conditions=step_data.get('conditions'),
                timeout=step_data.get('timeout'),
                fail_fast=step_data.get('fail_fast', True)
            ))
        return steps
    
    def _parse_test_steps(self, steps_data: List[Dict[str, Any]]) -> List[TestStep]:
        """Parse test steps from configuration data."""
        steps = []
        for step_data in steps_data:
            steps.append(TestStep(
                name=step_data.get('name'),
                command=step_data.get('command'),
                working_dir=step_data.get('working_dir'),
                environment=step_data.get('environment'),
                expected_files=step_data.get('expected_files'),
                timeout=step_data.get('timeout')
            ))
        return steps
    
    def _parse_validation_rules(self, rules_data: List[Dict[str, Any]]) -> List[ValidationRule]:
        """Parse validation rules from configuration data."""
        rules = []
        for rule_data in rules_data:
            rules.append(ValidationRule(
                name=rule_data.get('name'),
                type=rule_data.get('type'),
                target=rule_data.get('target'),
                parameters=rule_data.get('parameters')
            ))
        return rules
    
    def _validate_file_exists_rule(self, rule: ValidationRule) -> List[str]:
        """Validate file_exists validation rule."""
        errors = []
        if not rule.target:
            errors.append(f"Validation rule '{rule.name}': target file path is required")
        return errors
    
    def _validate_command_success_rule(self, rule: ValidationRule) -> List[str]:
        """Validate command_success validation rule."""
        errors = []
        if not rule.target:
            errors.append(f"Validation rule '{rule.name}': target command is required")
        return errors
    
    def _validate_regex_match_rule(self, rule: ValidationRule) -> List[str]:
        """Validate regex_match validation rule."""
        errors = []
        if not rule.target:
            errors.append(f"Validation rule '{rule.name}': target file path is required")
        if not rule.parameters or 'pattern' not in rule.parameters:
            errors.append(f"Validation rule '{rule.name}': 'pattern' parameter is required")
        return errors
    
    def _validate_env_var_rule(self, rule: ValidationRule) -> List[str]:
        """Validate environment_var validation rule."""
        errors = []
        if not rule.target:
            errors.append(f"Validation rule '{rule.name}': target environment variable is required")
        return errors
    
    def _validate_directory_not_empty_rule(self, rule: ValidationRule) -> List[str]:
        """Validate directory_not_empty validation rule."""
        errors = []
        if not rule.target:
            errors.append(f"Validation rule '{rule.name}': target directory path is required")
        return errors
    
    def _validate_executable_exists_rule(self, rule: ValidationRule) -> List[str]:
        """Validate executable_exists validation rule."""
        errors = []
        if not rule.target:
            errors.append(f"Validation rule '{rule.name}': target executable path is required")
        return errors
    
    def _validate_library_exists_rule(self, rule: ValidationRule) -> List[str]:
        """Validate library_exists validation rule."""
        errors = []
        if not rule.target:
            errors.append(f"Validation rule '{rule.name}': target library path is required")
        return errors


# Global parser instance
config_parser = ExtensionConfigParser()